<template>
  <div :id="domId" ref="domId" class="container" :class="customClassName">
    <!-- 构件树 -->
    <div v-if="showTree && useCustomTree" class="model-tree-container" :style="{'left': left, 'top': top}">
      <div class="model-tree-title">
        <div class="title-text">构件树</div>
        <img src="./images/modelTreeClose.png" class="model-tree-close" @click="close" />
      </div>
      <div v-if="isSearch" class="search">
        <el-input v-model="searchName" prefix-icon="el-icon-search" placeholder="请输入构件名称" />
      </div>
      <div v-if="searchName" class="search-wrapper large-screen-scroll">
        <div v-for="(item, index) in searchResult" :key="index" class="search-item" @click="treeNodeClickHandle({data: item}, $event)">
          <div>{{ item.name }}</div>
          <div class="gray">{{ item.fullName }}</div>
        </div>
      </div>
      <div class="model-tree-content large-screen-scroll">
        <el-tree
          ref="eleTree"
          :expand-on-click-node="false"
          default-expand-all
          :highlight-current="true"
          :props="defaultProps"
          node-key="id"
          :data="elementTreeData"
        >
          <div slot-scope="{ node }" class="custom-tree-node">
            <span
              :id="node.data.id"
              style="color:white"
              :class="{ active: getActive(node.data.id) }"
              @click="treeNodeClickHandle(node, $event)"
            >{{ node.data.name }}</span>
            <i class="show-model-icon el-icon-view" :class="{ 'icon-active': node.data.showModel }" @click="hideComponents(node)" />
          </div>
        </el-tree>
      </div>
    </div>
    <!-- 右键菜单 -->
    <div v-if="showMenu" class="custom-menu" :style="{'left': menuPosition.left + 'px', 'top': menuPosition.top + 'px'}">
      <template v-if="selectedComponents.length === 1">
        <div v-for="(item, index) in customRightMenu" :key="index" class="custom-menu-item" @click="menuClick(item)">{{ item.name }}</div>
      </template>
      <div v-if="selectedComponents.length !== 0" class="custom-menu-item" @click="hideSelectedComponents">隐藏选中构件</div>
      <div class="custom-menu-item" @click="showAllComponents">显示所有构件</div>
    </div>
  </div>
</template>
<script>
/** ---------------------------------------------------------
 * viewToken参数，用于加载模型（必填）
 * customClassName参数，用于自定义类名
 * backgroundColor参数，用于设置背景色
 * backgroundOpacity参数，用于设置背景透明度
 * useCustomTree: 如需添加构件树，置为true
 * elementTreeData: 构件树数据,包含name,children,leaf,id
 * showModelTree: 构件树显隐，需要添加 .sync 修饰符
 * left，top: 构件树位置
 * customRightMenu: 自定义右键菜单（右键模型时的菜单列表，包含菜单文字和事件名称）
 * modelStateLists：用于设置构件变色数据，包含颜色:color,构件id（bimface中的构件id）:id
 * extraModelData: 用于设置额外模型数据，如：测点数据等，显示为圆形，必需包含text,position,color,id
 * 若有customRightMenu，抛出customRightMenu中定义的事件名称，用于监听自定义右键菜单点击事件，参数为点击的模型对应的构件树节点(若有)/模型elementId
 * 若有extraModelData，抛出extraModelClick事件，用于监听额外模型点击事件，参数为extraModelData中的源数据
 * */
const BimfaceSDKLoaderConfig = window.BimfaceSDKLoaderConfig
const BimfaceSDKLoader = window.BimfaceSDKLoader
const Glodon = window.Glodon
export default {
  name: 'BimfaceCom',
  props: {
    defaultTreeId: {
      type: String,
      default: ''
    },
    // 自定义类名
    customClassName: {
      type: String,
      default: ''
    },
    // 是否使用自定义tree
    useCustomTree: {
      type: Boolean,
      default: true
    },
    // 集成模型id模型fileId，若为集成模型，必需添加该值
    fileId: {
      type: String,
      default: ''
    },
    // 模型token
    viewToken: {
      type: String,
      default: '',
      require: true
    },
    // 背景色
    backgroundColor: {
      type: String,
      default: '#d6e0eb'
    },
    // 背景透明度
    backgroundOpacity: {
      type: Number,
      default: 1
    },
    // 构件变色数据-> 包含颜色:color,构件id（bimface中的构件id）:id
    modelStateLists: {
      type: Array,
      default: () => []
    },
    // 构件树数据
    elementTreeData: {
      type: Array,
      default: () => []
    },
    // 控制构件树显隐,需要添加 .sync 修饰符
    showModelTree: {
      type: Boolean,
      default: false
    },
    // 构件树左侧距离，可添加绝对值和%
    left: {
      type: String,
      default: '100px'
    },
    top: {
      type: String,
      default: '0'
    },
    // 构件树是否添加搜索
    isSearch: {
      type: Boolean,
      default: true
    },
    // 自定义右键菜单
    customRightMenu: {
      type: Array,
      default: () => []
    },
    // 测点数据 为空则不显示 必需text,position,color,id
    extraModelData: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      domId: 'domId' + new Date().getTime(),
      // 点击的屏幕坐标
      clickScreenPosition: { left: 0, top: 0 },
      // 构件树list结构
      elementTreeList: [],
      // 构件树查询
      searchName: '',
      searchResult: [],
      defaultProps: {
        label: 'name',
        children: 'children',
        isLeaf: 'leaf'
      },
      currentTreeId: [],
      showTree: this.showModelTree,
      showMenu: false,
      menuPosition: {
        left: 0,
        top: 0
      },
      rightClickTreeNode: null,
      modelSuccess: false,
      selectedComponents: []
    }
  },
  watch: {
    viewToken() {
      if (this.model3D) this.viewer3D.removeModel(this.model3D.modelId)
      if (this.viewToken && this.viewer3D) {
        this.viewer3D.loadModel({
          viewToken: this.viewToken
        })
      }
    },
    elementTreeData: {
      handler() {
        this.elementTreeData.forEach((item) => {
          this.parseTreeToList(item, this.elementTreeList)
        })
      },
      immediate: true
    },
    searchName() {
      this.searchResult = this.elementTreeList.filter((item) =>
        item.name.includes(this.searchName)
      )
    },
    showModelTree() {
      this.showTree = this.showModelTree
      if (this.showTree) {
        this.$nextTick(() => {
          if (this.defaultTreeId || this.currentTreeId.length > 0) {
            const node = this.$refs.eleTree.getNode(this.defaultTreeId)
            this.treeNodeClickHandle(node, {})
          }
        })
      }
    },
    modelStateLists() {
      if (this.modelStateLists.length > 0) this.changeModelColor()
    }
  },
  mounted() {
    this.$refs.domId.addEventListener('click', (e) => {
      this.clickScreenPosition.left = e.clientX
      this.clickScreenPosition.top = e.clientY
    })
    this.initModel()
  },
  beforeDestroy() {
    if (this.viewer3D) this.viewer3D.destroy()
  },
  methods: {
    initModel() {
      const options = new BimfaceSDKLoaderConfig()
      options.viewToken = this.viewToken
      BimfaceSDKLoader.load(options, this.successCallback, this.failureCallback)
    },
    successCallback(viewMetaData) {
      if (viewMetaData.viewType === '3DView') {
        const domElement = document.getElementById(this.domId)
        const webAppConfig =
          new Glodon.Bimface.Application.WebApplication3DConfig()
        webAppConfig.domElement = domElement
        // 设置全局单位
        webAppConfig.globalUnit =
          Glodon.Bimface.Common.Units.LengthUnits.Millimeter
        // 清空默认toolbars
        webAppConfig.Toolbars = []
        webAppConfig.Buttons = []
        webAppConfig.maxMemoryUsage = 1024
        const app = new Glodon.Bimface.Application.WebApplication3D(
          webAppConfig
        )
        app.addView(this.viewToken)
        const viewer3D = app.getViewer()
        this.viewer3D = viewer3D
        this.app = app
        // 设置曝光度
        // webAppConfig.exposure = 0
        // 是否启用环境光照
        // webAppConfig.enableIBLBackground = false
        setTimeout(() => {
          viewer3D.setBackgroundColor(
            new Glodon.Web.Graphics.Color(
              this.backgroundColor,
              this.backgroundOpacity
            )
          )
        }, 1000)
        viewer3D.toggleContextMenuDisplay(false)
        this.addEvent()
      }
    },
    hideComponents(node) {
      if (this.model3D) {
        const ids = this.getElementsIdByNode(node.data)
        node.data.showModel = !node.data.showModel
        node.data.showModel
          ? this.showComponentsById(ids)
          : this.hideComponentsById(ids)
      }
      this.showMenu = false
    },
    hideComponentsById(ids) {
      const fileIds = this.getElementIdsWithFileId(ids)
      this.model3D.hideComponentsById(fileIds)
    },
    showComponentsById(ids) {
      const fileIds = this.getElementIdsWithFileId(ids)
      this.model3D.showComponentsById(fileIds)
    },
    hideSelectedComponents() {
      if (this.model3D && this.selectedComponents.length > 0) {
        const ids = []
        this.selectedComponents.forEach((item) => {
          if (item.associatedElements.includes(',')) {
            ids.push(...item.associatedElements.split(','))
          } else {
            ids.push(item.associatedElements)
          }
        })
        const fileIds = this.getElementIdsWithFileId(ids)
        this.model3D.hideComponentsById(fileIds)
        this.showMenu = false
      }
    },
    showAllComponents() {
      if (this.model3D) {
        this.model3D.showAllComponents()
      }
      this.showMenu = false
    },
    addEvent() {
      const viewer3D = this.viewer3D
      viewer3D.addEventListener(
        Glodon.Bimface.Viewer.Viewer3DEvent.ViewAdded,
        () => {
          // 自适应屏幕大小
          window.onresize = function () {
            viewer3D.resize(
              document.documentElement.clientWidth,
              document.documentElement.clientHeight - 40
            )
          }
          this.setCameraRange()
          // 绘制测点
          if (this.extraModelData.length > 0) {
            this.addMarkerable()
            this.addDrawable()
            this.initExtModel()
          }
          this.model3D = viewer3D.getModel()
          if (this.modelStateLists.length > 0) this.changeModelColor()
          if (this.fileId) {
            const homeview = {
              name: 'persp',
              position: {
                x: -397983.3188570747,
                y: -1458396.0983519668,
                z: 59744.60464995734
              },
              target: {
                x: 2367124.5399919813,
                y: 2985445.943076001,
                z: -1933308.5944634143
              },
              up: {
                x: 0.18801070064332373,
                y: 0.3021505959622272,
                z: 0.9345357102878827
              },
              near: 557.8386889954525,
              far: 2994655.0581701817,
              zoom: 0.1892426633588279,
              version: 1,
              fov: 45,
              aspect: 2.233009708737864,
              coordinateSystem: 'world'
            }
            viewer3D.setCameraStatus(homeview)
            viewer3D.recordCustomHomeview(homeview)
            // 渲染3D模型
            viewer3D.render()
          }
          this.$emit('modelLoaded')
          // const lightManage = viewer3D.getLightManager()
          // lightManage.clear()
          // lightManage.enableAllLights(false)
        }
      )
      viewer3D.addEventListener(
        Glodon.Bimface.Viewer.Viewer3DEvent.MouseClicked,
        (data) => {
          // 获取视角信息
          // const homeview = viewer3D.getCameraStatus()
          // console.log(homeview, 'homeview')
          this.showMenu = false
          const selected = this.model3D.getSelectedComponents()
          let treeSelected = []
          selected.forEach((item) => {
            this.elementTreeList.forEach((el) => {
              const elements = this.fileId
                ? this.fileId + '.' + el.associatedElements
                : el.associatedElements

              if (elements && elements.includes(item)) {
                treeSelected.push(el)
              }
            })
          })
          treeSelected = [...new Set(treeSelected)]
          this.selectedComponents = treeSelected
          if (data.eventType === 'Click') {
            this.showMenu = false
            this.rightClickTreeNode = null
            this.currentTreeId = []
            if (treeSelected.length > 0) {
              const last = treeSelected[treeSelected.length - 1]
              this.$emit('modelClick', last, treeSelected)
              const id = last.id
              if (this.$refs.eleTree) {
                this.$refs.eleTree.setCurrentKey(null)
                const nodeDom = document.getElementById(id)
                setTimeout(() => {
                  if (nodeDom) {
                    this.$nextTick(() => {
                      nodeDom.scrollIntoView({ block: 'center' }) // 通过scrollIntoView方法将对应的dom元素定位到可见区域 【block: 'center'】这个属性是在垂直方向居中显示
                    })
                  }
                }, 100)
              }
            } else {
              this.$emit('modelClick', {}, [])
            }
            if (data.elementId) {
              treeSelected.forEach((item) => {
                this.currentTreeId.push(item.id)
              })
            }
          }
          const subData =
            this.currentTreeId.length === 0
              ? { subSectionId: '' }
              : treeSelected[0]
          const sendData = { data: subData }
          this.$emit('treeNodeClick', sendData, this.currentTreeId.length === 0)
          if (data.eventType === 'RightClick') {
            const { x, y } = data.clientPosition
            this.menuPosition.left = x + 10
            this.menuPosition.top = y
            this.showMenu = true
            if (treeSelected.length === 1) {
              this.rightClickTreeNode = treeSelected[0]
            }
          }
        }
      )
    },
    getActive(id) {
      return this.currentTreeId.some((item) => item === id)
    },
    menuClick(item) {
      this.showMenu = false
      this.$emit(item.emit, this.rightClickTreeNode)
    },
    // 根据不同状态进行颜色显示
    changeModelColor() {
      // 相同颜色进行组合
      const changeColor = {}
      this.modelStateLists.forEach((item) => {
        if (!changeColor[item.color]) {
          changeColor[item.color] = []
        }
        changeColor[item.color].push(item.id)
      })
      for (const key in changeColor) {
        const item = changeColor[key]
        const c = new Glodon.Web.Graphics.Color(key, 1)
        const fileIds = this.getElementIdsWithFileId(item)
        if (this.model3D) this.model3D.overrideComponentsColorById(fileIds, c)
      }
    },
    // 设置相机缩放范围
    setCameraRange() {
      this.viewer3D.setMaximalRangeofCamera(1.5)
    },
    // 清除模型选中状态
    clearSelectedModel() {
      if (this.model3D) {
        this.model3D.clearSelectedComponents()
      }
    },
    getElementIdsWithFileId(ids) {
      return this.fileId
        ? ids.map((item) => this.fileId + '.' + item.toString())
        : ids
    },
    // 设置模型选中状态
    setSelected(ids, clear = true) {
      const fileIds = this.getElementIdsWithFileId(ids)
      if (this.model3D) {
        if (clear) this.clearSelectedModel()
        clear
          ? this.model3D.setSelectedComponentsById(fileIds)
          : this.model3D.addSelectedComponentsById(fileIds)
        this.model3D.zoomToSelectedComponents()
        this.viewer3D.render()
      }
    },
    removeSelectedId(ids) {
      const fileIds = this.getElementIdsWithFileId(ids)
      this.model3D.removeSelectedId(fileIds)
      this.viewer3D.render()
    },
    getElementsIdByNode(data, addId = true) {
      const floors = []
      this.getTreeFloor(data, floors)
      const ids = []
      floors.forEach((item) => {
        if (item.associatedElements) {
          if (addId) {
            if (item.associatedElements.includes(',')) {
              ids.push(...item.associatedElements.split(','))
            } else {
              ids.push(item.associatedElements)
            }
          } else {
            ids.push(item)
          }
        }
      })
      return ids
    },
    treeNodeClickHandle(node, event) {
      const { data } = node
      const items = this.getElementsIdByNode(data, false)
      const ids = []
      items.forEach((item) => {
        if (item.associatedElements.includes(',')) {
          ids.push(...item.associatedElements.split(','))
        } else {
          ids.push(item.associatedElements)
        }
      })
      const findIndex = this.currentTreeId.findIndex(
        (item) => item === node.data.id
      )
      if (findIndex !== -1) {
        // 取消选中
        this.selectedComponents = []
        this.currentTreeId.splice(findIndex, 1)
        const fileIds = this.getElementIdsWithFileId(ids)
        this.model3D.removeSelectedId(fileIds)
        this.viewer3D.render()
        this.$emit('treeNodeClick', node, false)
        return
      }
      event = event || {}
      if (!event.ctrlKey) {
        this.currentTreeId = []
      }
      this.selectedComponents = items
      this.currentTreeId.push(data.id)
      this.searchName = ''
      this.searchResult = []
      this.setSelected(ids, !event.ctrlKey)
      this.$emit('treeNodeClick', node)
    },
    // 获取树list结构
    parseTreeToList(data, res = []) {
      this.$set(data, 'showModel', true)
      res.push(data)
      if (data.children && data.children.length > 0) {
        data.children.forEach((item) => {
          this.parseTreeToList(item, res)
        })
      }
    },
    // 获取树的最底层节点
    getTreeFloor(data, res = []) {
      if (data.children && data.children.length > 0) {
        data.children.forEach((item) => {
          this.getTreeFloor(item, res)
        })
      } else {
        res.push(data)
      }
    },
    close() {
      this.$emit('update:showModelTree', false)
    },
    addMarkerable() {
      const markerConfig =
        new Glodon.Bimface.Plugins.Marker3D.Marker3DContainerConfig()
      markerConfig.viewer = this.viewer3D
      this.marker = new Glodon.Bimface.Plugins.Marker3D.Marker3DContainer(
        markerConfig
      )
    },
    addDrawable() {
      const drawableConfig =
        new Glodon.Bimface.Plugins.Drawable.DrawableContainerConfig()
      drawableConfig.viewer = this.viewer3D
      this.drawable = new Glodon.Bimface.Plugins.Drawable.DrawableContainer(
        drawableConfig
      )
    },
    initExtModel() {
      this.markerObj = {}
      this.extraModelData.forEach((item) => {
        this.drawCircle(item)
      })
      this.viewer3D.render()
    },
    // 画圆canvas
    drawCircle(item, border = false) {
      const marker3dConfig =
        new Glodon.Bimface.Plugins.Marker3D.Marker3DConfig()
      marker3dConfig.hoverAnimation = false
      const canvas = this.createCanvas(item.color, border)
      marker3dConfig.canvas = canvas
      // marker3dConfig.size = 100
      marker3dConfig.worldPosition = item.position
      const marker3d = new Glodon.Bimface.Plugins.Marker3D.Marker3D(
        marker3dConfig
      )
      marker3d.customId = item.id
      marker3d.onClick((el) => {
        const id = el.customId
        for (const key in this.markerObj) {
          const m = this.markerObj[key]
          if (m.active) {
            m.active = false
            this.marker.removeItemById(m.id)
            this.drawable.removeItemById(m.label.id)
            this.drawCircle(m.data)
          }
        }
        this.marker.removeItemById(el.id)
        this.drawable.removeItemById(el.label.id)
        this.drawCircle(el.data, true)
        this.markerObj[id].active = true
        const { left, top } = this.clickScreenPosition
        const emitData = {
          ...el.data,
          left,
          top
        }
        this.$emit('extraModelClick', emitData)
      })
      this.marker.addItem(marker3d)
      marker3d.label = this.addLabel(item.text, item.position, item.color)
      marker3d.data = item
      this.markerObj[item.id] = marker3d
    },
    addLabel(text, worldPosition, color) {
      const config = new Glodon.Bimface.Plugins.Drawable.LeadLabelConfig()
      // 引线折点的相对位置
      config.offset = { x: 27, y: -47 }
      // 引线标签的内容
      config.text = text
      // 引线标签的世界坐标
      worldPosition.x = worldPosition.x + 10
      worldPosition.y = worldPosition.y + 10
      config.worldPosition = worldPosition
      // 引线标签是否可拖拽
      config.draggable = false
      // 引线标签的视图
      config.viewer = this.viewer3D
      config.style = {
        lineColor: new Glodon.Web.Graphics.Color(color, 0.5),
        backgroundColor: new Glodon.Web.Graphics.Color(color, 0.5),
        fontSize: 10,
        lineWidth: 1
      }
      const label = new Glodon.Bimface.Plugins.Drawable.LeadLabel(config)
      this.drawable.addItem(label)
      return label
    },
    createCanvas(fillColor = '#32D3A6', border = false) {
      const width = 40
      const height = 40
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      canvas.width = width
      canvas.height = height
      ctx.fillStyle = fillColor
      ctx.strokeStyle = '#fff'
      ctx.lineWidth = 3
      ctx.arc(20, border ? 27 : 30, 10, 0, 2 * Math.PI)
      ctx.fill()
      if (border) ctx.stroke()
      return canvas
    }
  }
}
</script>
<style lang="scss" scoped>
.container {
  width: 100%;
  height: 100%;
  position: relative;
  ::v-deep.bf-drawable-lead-point {
    opacity: 0;
    // r: 10;
    // stroke-width: 0 !important;
  }
  ::v-deep.bf-drawable-lead-label {
    color: #fff;
  }
  .model-tree-container {
    width: 290px;
    height: 480px;
    background-image: url(./images/modelTreeBg.png);
    background-size: 100% 100%;
    padding: 0 10px 10px 10px;
    position: absolute;
    z-index: 9;
    top: 0;
    left: 100px;
    .model-tree-title {
      width: 100%;
      height: 50px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      background-image: url(./images/modelTreeTitleBottomBorder.png);
      background-repeat: no-repeat;
      background-position: bottom;
      .title-text {
        width: 70px;
        height: 28px;
        text-align: center;
        line-height: 28px;
        color: #fff;
        border-radius: 4px;
        background: rgba(34, 126, 195, 0.4);
        border: 1px solid rgba(7, 162, 245, 0.3);
        font-size: 12px;
      }
      .model-tree-close {
        width: 18px;
        height: 16px;
        cursor: pointer;
      }
    }
    .search {
      margin-top: 10px;
      ::v-deep.el-input__inner {
        background: rgba(0, 0, 0, 0);
        color: #fff;
      }
    }
    .search-wrapper {
      width: 100%;
      height: 300px;
      position: absolute;
      background: #092845;
      opacity: 0.6;
      top: 103px;
      left: 0;
      z-index: 9;
      overflow-y: auto;
      font-size: 14px;
      color: #fff;
      padding: 0 10px;
      border: 1px solid #224d70;
      .search-item {
        cursor: pointer;
        margin: 5px 0;
      }
      .search-item:hover {
        color: #32d3a6;
      }
      .gray {
        color: #ccc;
        font-size: 12px;
        padding-left: 10px;
      }
    }
  }
  .custom-menu {
    width: fit-content;
    position: absolute;
    z-index: 9;
    background: rgba(13, 58, 89, 0.5);
    border: 2px solid rgba(85, 155, 224, 0.6);
    .custom-menu-item {
      height: 30px;
      line-height: 30px;
      font-size: 12px;
      color: #fff;
      padding: 0 10px;
      overflow: hidden;
      cursor: pointer;
      user-select: none;
      background: rgba(38, 134, 224, 0.4);
      border-radius: 2px;
      margin: 5px;
      border: 1px solid rgba(85, 155, 224, 0.6);
    }
  }
}
</style>
<style lang="scss">
.model-tree-content {
  width: 100%;
  height: calc(100% - 100px);
  overflow-y: auto;
  font-size: 12px;
  .el-tree {
    background-color: rgba(0, 0, 0, 0) !important;
  }
  .el-tree-node__content {
    background-color: rgba(0, 0, 0, 0) !important;
  }
  .el-tree-node__content {
    opacity: 0.8;
    background-color: rgba(250, 250, 250, 1);
  }

  .active {
    color: #32d3a6 !important;
  }
  .custom-tree-node {
    width: 100%;
    padding-right: 5px;
    .show-model-icon {
      margin-top: 3px;
      float: right;
      color: #ccc;
    }
    .icon-active {
      color: #32d3a6;
    }
  }
}
.bf-house {
  transform: scale(0.8);
}
.bf-menu {
  background: rgba(13, 58, 89, 0.5) !important;
  border: 2px solid rgba(85, 155, 224, 0.6) !important;
  .bf-spacer {
    display: none;
  }
  .bf-menu-item {
    color: #fff;
    background: rgba(38, 134, 224, 0.4);
    border-radius: 2px;
    margin: 5px;
    border: 1px solid rgba(85, 155, 224, 0.6);
  }
}
</style>
